/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package vmwapplication.parser.solver;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import vmwapplication.data.IDAO;
import vmwapplication.data.Test;

/**
 *
 * @author marek
 */
public class SmithWatermanSolver implements Solver {

    private IDAO dao;
    
    private final int DIAGONAL_MOVEMENT = 0;
    
    private final int LEFT_MOVEMENT = 1;
    
    private final int UP_MOVEMENT = 2;
    
    private final int A=0, G=1, C=2, T=3;
   
    private final int d = -2;
                                   // A    G   C   T
    private final int[][] simularity = {{2, -1, -1, -1}, //A
                                        {-1,  2, -1, -1}, //G
                                        {-1, -1,  2,  -1}, //C
                                        {-1, -1,  -1,  2}};//T 
    
    public IDAO getDao() {
        return dao;
    }

    public void setDao(IDAO dao) {
        this.dao = dao;
    }

    public List<Result> solve(String dnaString) {
        List<Test> strings = dao.findAll();
        List<Result> results = new ArrayList<Result>();
        for(Test test : strings){
            SmithWatermanBestCoordinate best = new SmithWatermanBestCoordinate();
            int[][] hMatrix = computeHMatrix(test.getDnastring(), dnaString, best);
//            for(int i = 0 ; i< hMatrix.length; i++){
//                for(int j = 0 ; j< hMatrix[i].length; j++){
//                    System.out.print(hMatrix[i][j]+"\t");
//                }
//                System.out.println();
//            }
            
            Result res = getAlligment(hMatrix, test.getDnastring(), dnaString, best);
            results.add(res);
        }
        Collections.sort(results);
        return results;
    }
    
    private Result getAlligment(int[][] hMatrix, String a, String b,SmithWatermanBestCoordinate best){
        String aligmentA = "";
        String aligmentB = "";
        String slashes="";
        
        Result res = new Result();
        int i = best.getI();
        int j = best.getJ();
        
        
        if(i < a.length()){
            aligmentA = a.substring(i, a.length()-1);
            for(int l = i; l<a.length()-1; l++){
                aligmentB = aligmentB + "-";
            }
        }
        
        
        Integer aligmentvalue = hMatrix[i][j];
        res.setAligmentValue(aligmentvalue);
        
        while(i > 0 && j > 0 ){
            SmithWatermanBestCoordinate bestStepCoor = new SmithWatermanBestCoordinate();
            int bestValue = hMatrix[i-1][j];
            bestStepCoor.setI(i-1);
            bestStepCoor.setJ(j);
            int directionMovement = LEFT_MOVEMENT;
            
            if(hMatrix[i][j-1] > bestValue){
                bestValue = hMatrix[i][j-1];
                bestStepCoor.setI(i);
                bestStepCoor.setJ(j-1);
                directionMovement = UP_MOVEMENT;
            }
            
            if(hMatrix[i-1][j-1] >= bestValue){
                bestValue = hMatrix[i-1][j-1];
                bestStepCoor.setI(i-1);
                bestStepCoor.setJ(j-1);
                directionMovement = DIAGONAL_MOVEMENT;
            }
            
            switch(directionMovement){
                case DIAGONAL_MOVEMENT:{
                    aligmentA = a.charAt(i-1) + aligmentA;
                    aligmentB = b.charAt(j-1) + aligmentB;
                    if(a.charAt(i-1) == b.charAt(j-1)){
                        slashes = "|" + slashes;
                    }else{
                        slashes = " " + slashes;
                    }
                    i--;j--;
                }break;
                case LEFT_MOVEMENT:{
                    aligmentA = a.charAt(i-1) + aligmentA;
                    aligmentB = "-" + aligmentB;
                    slashes = " " + slashes;
                    i--;
                }break;
                case UP_MOVEMENT:{
                    slashes = " " + slashes;
                    aligmentA = "-" + aligmentA;
                    aligmentB = b.charAt(j-1) + aligmentB;
                    j--;
                }break;    
            }
            
        }
        
        while(i > 0){
                aligmentA = a.charAt(i-1) + aligmentA;
                aligmentB = "-" + aligmentB;
                slashes = " " + slashes;
                i--;
            }
            
        while(j > 0){
            aligmentA = "-" + aligmentA;
            aligmentB = b.charAt(j-1) + aligmentB;
            slashes = " " + slashes;
            j--;
        }

        res.setAligmentA(aligmentA);
        res.setSlashes(slashes);
        res.setAligmentB(aligmentB);
        return res;
    }
    
    
    private int[][] computeHMatrix(String stringA, String stringB, SmithWatermanBestCoordinate best){
        int lenghtA = stringA.length() +1;
        int lenghtB = stringB.length() +1;
        int bestValue = 0;
        int[][] fMatrix = new int[lenghtA][lenghtB];
        for(int i=0; i<lenghtA; i++){
            fMatrix[i][0] = 0;
        }
        
        for(int i=0; i<lenghtB; i++){
            fMatrix[0][i] = 0;
        }
        
        for(int i=1;i<lenghtA;i++){
            for(int j=1;j<lenghtB;j++){
                int match = fMatrix[i-1][j-1] + wFunction(stringA.charAt(i-1), stringB.charAt(j-1));
                int delete = maxInColumn(fMatrix[i],j) + d;
                int insert = maxInRow(i,j,fMatrix ) + d;
                fMatrix[i][j] = max(0,match,delete,insert);
                if(fMatrix[i][j] >= bestValue){
                    best.setI(i);
                    best.setJ(j);
                    bestValue = fMatrix[i][j];
                }
            }
        }
        return fMatrix;
    }
    
    private int maxInColumn(int[] column, int lastIndex){
        int max = 0;
        for(int i = 0; i < lastIndex; i++){
            if(column[i]> max){
                max = column[i];
            }
        }
        return max;
    }
    
    private int maxInRow(final int rowIndex, int columnLastIndex, int[][] hMatrix){
        int max = 0;
        for(int i = 0; i < rowIndex; i++ ){
            if(hMatrix[i][columnLastIndex] > max ){
                max = hMatrix[i][columnLastIndex];
            }
        }
        return max;
    }
    
    private int wFunction(char A, char B){
        int aVal = translate(A);
        int bVal = translate(B);
        return simularity[aVal][bVal];
    }
    
    private int max(int r,int a, int b, int c){
        int maxA = Math.max(a, b);
        int maxB = Math.max(r, c);
        return Math.max(maxA, maxB);
    }
    
    private int translate(char a){
        int i = 0;
        switch(a){
            case 'A':{i = A;}break;
            case 'G':{i = G;}break;
            case 'C':{i = C;}break;
            case 'T':{i = T;}break;
        }
        return i;
    }
    
}
